Micron Document
🎖️GitЯра🎖️


Displaying Raw • Download

core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/NodeInfoDao.kt 9acdf5309f8f0ab96b30d6505bdac5e93a3bb72c (9acdf530) Text, 13.04 KB

T8b949e/*
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.core.database.dao

Tff7b72import T7ee787androidx.room3.Dao
Tff7b72import T7ee787androidx.room3.MapColumn
Tff7b72import T7ee787androidx.room3.Query
Tff7b72import T7ee787androidx.room3.Transaction
Tff7b72import T7ee787androidx.room3.Upsert
Tff7b72import T7ee787kotlinx.coroutines.flow.Flow
Tff7b72import T7ee787okio.ByteString
Tff7b72import T7ee787org.meshtastic.core.database.entity.MetadataEntity
Tff7b72import T7ee787org.meshtastic.core.database.entity.MyNodeEntity
Tff7b72import T7ee787org.meshtastic.core.database.entity.NodeEntity
Tff7b72import T7ee787org.meshtastic.core.database.entity.NodeWithRelations
Tff7b72import T7ee787org.meshtastic.proto.HardwareModel

Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffTooManyFunctionsTa5d6ff"Tb4b4b4)
Tf0883e@Dao
Tff7b72interface T56d364NodeInfoDao Tb4b4b4{

Tff7b72companion Tff7b72object Tb4b4b4{
Tff7b72const Tff7b72val Te6edf3KEY_SIZE Tff7b72= T79c0ff3T79c0ff2
Tb4b4b4}

T8b949e/**
* Verifies a [NodeEntity] before an upsert operation. It handles populating the publicKey for lazy migration,
* checks for public key conflicts with new nodes, and manages updates to existing nodes, particularly in cases of
* public key mismatches to prevent potential impersonation or data corruption.
*
* @param incomingNode The node entity to be verified.
* @return A [NodeEntity] that is safe to upsert, or null if the upsert should be aborted (e.g., due to an
* impersonation attempt, though this logic is currently commented out).
*/
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffgetVerifiedNodeForUpsertTb4b4b4(Te6edf3incomingNodeTb4b4b4: Te6edf3NodeEntityTb4b4b4)Tb4b4b4: Te6edf3NodeEntity Tb4b4b4{
T8b949e// Populate the NodeEntity.publicKey field from the User.publicKey for consistency
T8b949e// and to support lazy migration.
Te6edf3incomingNodeTb4b4b4.Te6edf3publicKey Tff7b72= Te6edf3incomingNodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3public_key

T8b949e// Populate denormalized name columns from the User protobuf for search functionality
T8b949e// Only populate if the user is not a placeholder (hwModel != UNSET); otherwise keep them null
Tff7b72if Tb4b4b4(Te6edf3incomingNodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3hw_model Tff7b72!Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3UNSETTb4b4b4) Tb4b4b4{
Te6edf3incomingNodeTb4b4b4.Te6edf3longName Tff7b72= Te6edf3incomingNodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3long_name
Te6edf3incomingNodeTb4b4b4.Te6edf3shortName Tff7b72= Te6edf3incomingNodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3short_name
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3incomingNodeTb4b4b4.Te6edf3longName Tff7b72= Tff7b72null
Te6edf3incomingNodeTb4b4b4.Te6edf3shortName Tff7b72= Tff7b72null
Tb4b4b4}

Tff7b72val Te6edf3existingNodeEntity Tff7b72= Te6edf3getNodeByNumTb4b4b4(Te6edf3incomingNodeTb4b4b4.Te6edf3numTb4b4b4)Tff7b72?.Te6edf3node

Tff7b72return Tff7b72if Tb4b4b4(Te6edf3existingNodeEntity Tff7b72=Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3handleNewNodeUpsertValidationTb4b4b4(Te6edf3incomingNodeTb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3handleExistingNodeUpsertValidationTb4b4b4(Te6edf3existingNodeEntityTb4b4b4, Te6edf3incomingNodeTb4b4b4)
Tb4b4b4}
Tb4b4b4}

T8b949e/** Validates a new node before it is inserted into the database. */
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffhandleNewNodeUpsertValidationTb4b4b4(Te6edf3newNodeTb4b4b4: Te6edf3NodeEntityTb4b4b4)Tb4b4b4: Te6edf3NodeEntity Tb4b4b4{
T8b949e// Check if the new node's public key (if present and not empty)
T8b949e// is already claimed by another existing node.
Tff7b72if Tb4b4b4(Tb4b4b4(Te6edf3newNodeTb4b4b4.Te6edf3publicKeyTff7b72?.Te6edf3size Tff7b72?: T79c0ff0Tb4b4b4) Tff7b72> T79c0ff0Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeWithSamePK Tff7b72= Te6edf3findNodeByPublicKeyTb4b4b4(Te6edf3newNodeTb4b4b4.Te6edf3publicKeyTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3nodeWithSamePK Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Te6edf3nodeWithSamePKTb4b4b4.Te6edf3num Tff7b72!Tff7b72= Te6edf3newNodeTb4b4b4.Te6edf3numTb4b4b4) Tb4b4b4{
T8b949e// This is a potential impersonation attempt.
Tff7b72return Te6edf3nodeWithSamePK
Tb4b4b4}
Tb4b4b4}
T8b949e// If no conflicting public key is found, or if the impersonation check is not active,
T8b949e// the new node is considered safe to add.
Tff7b72return Te6edf3newNode
Tb4b4b4}

T8b949e/**
* Resolves the public key for an existing node during an update.
*
* This function implements safety checks to prevent public key conflicts (PKC) and ensure robust handling of key
* updates.
*
* @param existingNode The current state of the node in the database.
* @param incomingNode The new node data being upserted.
* @return The resolved [ByteString] for the public key:
* - [NodeEntity.ERROR_BYTE_STRING]: If there is a mismatch between a valid existing key and a new incoming key.
* - `incomingNode.publicKey`: If the incoming key is new, matches the existing one, or if recovering from an error
* state.
* - `existingNode.publicKey`: If the incoming update has no key, or if the user is licensed but already has a valid
* key (prevents wiping).
* - [ByteString.EMPTY]: If the user is licensed and didn't previously have a key (or if key is explicitly cleared).
*/
Tff7b72private Tff7b72fun Td2a8ffresolvePublicKeyTb4b4b4(Te6edf3existingNodeTb4b4b4: Te6edf3NodeEntityTb4b4b4, Te6edf3incomingNodeTb4b4b4: Te6edf3NodeEntityTb4b4b4)Tb4b4b4: Te6edf3ByteString? Tb4b4b4{
Tff7b72val Te6edf3existingKey Tff7b72= Te6edf3existingNodeTb4b4b4.Te6edf3publicKey Tff7b72?: Te6edf3existingNodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3public_key
Tff7b72val Te6edf3incomingKey Tff7b72= Te6edf3incomingNodeTb4b4b4.Te6edf3publicKey

Tff7b72val Te6edf3incomingHasKey Tff7b72= Tb4b4b4(Te6edf3incomingKeyTff7b72?.Te6edf3size Tff7b72?: T79c0ff0Tb4b4b4) Tff7b72=Tff7b72= Te6edf3KEY_SIZE
Tff7b72val Te6edf3existingHasKey Tff7b72= Te6edf3existingKeyTb4b4b4.Te6edf3size Tff7b72=Tff7b72= Te6edf3KEY_SIZE Tff7b72&Tff7b72& Te6edf3existingKey Tff7b72!Tff7b72= Te6edf3NodeEntityTb4b4b4.Te6edf3ERROR_BYTE_STRING

Tff7b72return Tff7b72when Tb4b4b4{
Te6edf3incomingHasKey Tff7b72-Tff7b72> Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3existingHasKey Tff7b72&Tff7b72& Te6edf3incomingKey Tff7b72!Tff7b72= Te6edf3existingKeyTb4b4b4) Tb4b4b4{
T8b949e// Actual mismatch between two non-empty keys
Te6edf3NodeEntityTb4b4b4.Te6edf3ERROR_BYTE_STRING
Tb4b4b4} Tff7b72else Tb4b4b4{
T8b949e// New key, same key, or recovery from Error state
Te6edf3incomingKey
Tb4b4b4}
Tb4b4b4}
Te6edf3existingHasKey Tff7b72-Tff7b72> Te6edf3existingKey
Te6edf3incomingNodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3is_licensed Tff7b72-Tff7b72> Te6edf3ByteStringTb4b4b4.Te6edf3EMPTY
Tff7b72else Tff7b72-Tff7b72> Te6edf3existingKey
Tb4b4b4}
Tb4b4b4}

T8b949e/**
* Handles the validation logic when upserting an existing node.
*
* It distinguishes between two scenarios:
* 1. **Preservation**: If the incoming update is a placeholder (unset HW model) with a default name, and the
* existing node has full user info, we preserve the existing identity (user, keys, names, verification) while
* updating telemetry and status fields from the incoming packet.
* 2. **Update**: If it's a normal update, we validate the public key using [resolvePublicKey] to prevent conflicts
* or accidental key wiping, and then update the node.
*/
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffCyclomaticComplexMethodTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffMagicNumberTa5d6ff"Tb4b4b4)
Tff7b72private Tff7b72fun Td2a8ffhandleExistingNodeUpsertValidationTb4b4b4(Te6edf3existingNodeTb4b4b4: Te6edf3NodeEntityTb4b4b4, Te6edf3incomingNodeTb4b4b4: Te6edf3NodeEntityTb4b4b4)Tb4b4b4: Te6edf3NodeEntity Tb4b4b4{
Tff7b72val Te6edf3resolvedNotes Tff7b72= Te6edf3incomingNodeTb4b4b4.Te6edf3notesTb4b4b4.Te6edf3ifBlank Tb4b4b4{ Te6edf3existingNodeTb4b4b4.Te6edf3notes Tb4b4b4}

Tff7b72val Te6edf3isPlaceholder Tff7b72= Te6edf3incomingNodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3hw_model Tff7b72=Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3UNSET
Tff7b72val Te6edf3hasExistingUser Tff7b72= Te6edf3existingNodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3hw_model Tff7b72!Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3UNSET
Tff7b72val Te6edf3isDefaultName Tff7b72= Te6edf3incomingNodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3long_nameTb4b4b4.Te6edf3matchesTb4b4b4(Te6edf3RegexTb4b4b4(Ta5d6ff"Ta5d6ff^Meshtastic [0-9a-fA-F]{4}$Ta5d6ff"Tb4b4b4)Tb4b4b4)

Tff7b72if Tb4b4b4(Te6edf3hasExistingUser Tff7b72&Tff7b72& Te6edf3isPlaceholder Tff7b72&Tff7b72& Te6edf3isDefaultNameTb4b4b4) Tb4b4b4{
Tff7b72return Te6edf3incomingNodeTb4b4b4.Te6edf3copyTb4b4b4(
Te6edf3user Tff7b72= Te6edf3existingNodeTb4b4b4.Te6edf3userTb4b4b4,
Te6edf3publicKey Tff7b72= Te6edf3existingNodeTb4b4b4.Te6edf3publicKeyTb4b4b4,
Te6edf3longName Tff7b72= Te6edf3existingNodeTb4b4b4.Te6edf3longNameTb4b4b4,
Te6edf3shortName Tff7b72= Te6edf3existingNodeTb4b4b4.Te6edf3shortNameTb4b4b4,
Te6edf3manuallyVerified Tff7b72= Te6edf3existingNodeTb4b4b4.Te6edf3manuallyVerifiedTb4b4b4,
Te6edf3notes Tff7b72= Te6edf3resolvedNotesTb4b4b4,
Tb4b4b4)
Tb4b4b4}

Tff7b72val Te6edf3resolvedKey Tff7b72= Te6edf3resolvePublicKeyTb4b4b4(Te6edf3existingNodeTb4b4b4, Te6edf3incomingNodeTb4b4b4)

Tff7b72return Te6edf3incomingNodeTb4b4b4.Te6edf3copyTb4b4b4(
Te6edf3user Tff7b72= Te6edf3incomingNodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3public_key Tff7b72= Te6edf3resolvedKey Tff7b72?: Te6edf3ByteStringTb4b4b4.Te6edf3EMPTYTb4b4b4)Tb4b4b4,
Te6edf3publicKey Tff7b72= Te6edf3resolvedKeyTb4b4b4,
Te6edf3notes Tff7b72= Te6edf3resolvedNotesTb4b4b4,
Tb4b4b4)
Tb4b4b4}

Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffSELECT * FROM my_nodeTa5d6ff"Tb4b4b4)
Tff7b72fun Td2a8ffgetMyNodeInfoTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3FlowTff7b72<Te6edf3MyNodeEntity?Tff7b72>

Tf0883e@Upsert Tff7b72suspend Tff7b72fun Td2a8ffsetMyNodeInfoTb4b4b4(Te6edf3myInfoTb4b4b4: Te6edf3MyNodeEntityTb4b4b4)

Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffDELETE FROM my_nodeTa5d6ff"Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffclearMyNodeInfoTb4b4b4(Tb4b4b4)

Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff SELECT * FROM nodes
ORDER BY CASE
WHEN num = (SELECT myNodeNum FROM my_node LIMIT 1) THEN 0
ELSE 1
END,
last_heard DESC
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tf0883e@Transaction
Tff7b72fun Td2a8ffnodeDBbyNumTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3FlowTff7b72<
Te6edf3MapTff7b72<
Tf0883e@MapColumnTb4b4b4(Te6edf3columnName Tff7b72= Ta5d6ff"Ta5d6ffnumTa5d6ff"Tb4b4b4)
Tffa657IntTb4b4b4,
Te6edf3NodeWithRelationsTb4b4b4,
Tff7b72>Tb4b4b4,
Tff7b72>

Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff WITH OurNode AS (
SELECT latitude, longitude
FROM nodes
WHERE num = (SELECT myNodeNum FROM my_node LIMIT 1)
)
SELECT * FROM nodes
WHERE (:includeUnknown = 1 OR short_name IS NOT NULL)
AND (:filter = ''
OR (long_name LIKE '%' || :filter || '%'
OR short_name LIKE '%' || :filter || '%'
OR printf('!%08x', CASE WHEN num < 0 THEN num + 4294967296 ELSE num END) LIKE '%' || :filter || '%'
OR CAST(CASE WHEN num < 0 THEN num + 4294967296 ELSE num END AS TEXT) LIKE '%' || :filter || '%'))
AND (:lastHeardMin = -1 OR last_heard >= :lastHeardMin)
AND (:hopsAwayMax = -1 OR (hops_away <= :hopsAwayMax AND hops_away >= 0) OR num = (SELECT myNodeNum FROM my_node LIMIT 1))
ORDER BY CASE
WHEN num = (SELECT myNodeNum FROM my_node LIMIT 1) THEN 0
ELSE 1
END,
CASE
WHEN :sort = 'last_heard' THEN last_heard * -1
WHEN :sort = 'alpha' THEN UPPER(long_name)
WHEN :sort = 'distance' THEN
CASE
WHEN latitude IS NULL OR longitude IS NULL OR
(latitude = 0.0 AND longitude = 0.0) THEN 999999999
ELSE
(latitude - (SELECT latitude FROM OurNode)) *
(latitude - (SELECT latitude FROM OurNode)) +
(longitude - (SELECT longitude FROM OurNode)) *
(longitude - (SELECT longitude FROM OurNode))
END
WHEN :sort = 'hops_away' THEN
CASE
WHEN hops_away = -1 THEN 999999999
ELSE hops_away
END
WHEN :sort = 'channel' THEN channel
WHEN :sort = 'via_mqtt' THEN via_mqtt
WHEN :sort = 'via_favorite' THEN is_favorite * -1
ELSE 0
END ASC,
last_heard DESC
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tf0883e@Transaction
Tff7b72fun Td2a8ffgetNodesTb4b4b4(
Te6edf3sortTb4b4b4: Tffa657StringTb4b4b4,
Te6edf3filterTb4b4b4: Tffa657StringTb4b4b4,
Te6edf3includeUnknownTb4b4b4: Tffa657BooleanTb4b4b4,
Te6edf3hopsAwayMaxTb4b4b4: Tffa657IntTb4b4b4,
Te6edf3lastHeardMinTb4b4b4: Tffa657IntTb4b4b4,
Tb4b4b4)Tb4b4b4: Te6edf3FlowTff7b72<Te6edf3ListTff7b72<Te6edf3NodeWithRelationsTff7b72>Tff7b72>

Tf0883e@Transaction
Tff7b72suspend Tff7b72fun Td2a8ffclearNodeInfoTb4b4b4(Te6edf3preserveFavoritesTb4b4b4: Tffa657BooleanTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3preserveFavoritesTb4b4b4) Tb4b4b4{
Te6edf3deleteNonFavoriteNodesTb4b4b4(Tb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3deleteAllNodesTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}

Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffDELETE FROM nodes WHERE is_favorite = 0Ta5d6ff"Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffdeleteNonFavoriteNodesTb4b4b4(Tb4b4b4)

Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffDELETE FROM nodesTa5d6ff"Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffdeleteAllNodesTb4b4b4(Tb4b4b4)

Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffDELETE FROM nodes WHERE num=:numTa5d6ff"Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffdeleteNodeTb4b4b4(Te6edf3numTb4b4b4: Tffa657IntTb4b4b4)

Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffDELETE FROM nodes WHERE num IN (:nodeNums)Ta5d6ff"Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffdeleteNodesTb4b4b4(Te6edf3nodeNumsTb4b4b4: Te6edf3ListTff7b72<Tffa657IntTff7b72>Tb4b4b4)

Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffSELECT * FROM nodes WHERE last_heard < :lastHeardTa5d6ff"Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffgetNodesOlderThanTb4b4b4(Te6edf3lastHeardTb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Te6edf3NodeEntityTff7b72>

Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffSELECT * FROM nodes WHERE short_name IS NULLTa5d6ff"Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffgetUnknownNodesTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Te6edf3NodeEntityTff7b72>

Tf0883e@Upsert Tff7b72suspend Tff7b72fun Td2a8ffupsertTb4b4b4(Te6edf3metaTb4b4b4: Te6edf3MetadataEntityTb4b4b4)

Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffDELETE FROM metadata WHERE num=:numTa5d6ff"Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffdeleteMetadataTb4b4b4(Te6edf3numTb4b4b4: Tffa657IntTb4b4b4)

Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffSELECT * FROM nodes WHERE num=:numTa5d6ff"Tb4b4b4)
Tf0883e@Transaction
Tff7b72suspend Tff7b72fun Td2a8ffgetNodeByNumTb4b4b4(Te6edf3numTb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Te6edf3NodeWithRelations?

Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffSELECT * FROM nodes WHERE public_key = :publicKey LIMIT 1Ta5d6ff"Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8fffindNodeByPublicKeyTb4b4b4(Te6edf3publicKeyTb4b4b4: Te6edf3ByteString?Tb4b4b4)Tb4b4b4: Te6edf3NodeEntity?

Tf0883e@Upsert Tff7b72suspend Tff7b72fun Td2a8ffdoUpsertTb4b4b4(Te6edf3nodeTb4b4b4: Te6edf3NodeEntityTb4b4b4)

Tf0883e@Transaction
Tff7b72suspend Tff7b72fun Td2a8ffupsertTb4b4b4(Te6edf3nodeTb4b4b4: Te6edf3NodeEntityTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3verifiedNode Tff7b72= Te6edf3getVerifiedNodeForUpsertTb4b4b4(Te6edf3nodeTb4b4b4)
Te6edf3doUpsertTb4b4b4(Te6edf3verifiedNodeTb4b4b4)
Tb4b4b4}

Tf0883e@Upsert Tff7b72suspend Tff7b72fun Td2a8ffputAllTb4b4b4(Te6edf3nodesTb4b4b4: Te6edf3ListTff7b72<Te6edf3NodeEntityTff7b72>Tb4b4b4)

Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffUPDATE nodes SET notes = :notes WHERE num = :numTa5d6ff"Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffsetNodeNotesTb4b4b4(Te6edf3numTb4b4b4: Tffa657IntTb4b4b4, Te6edf3notesTb4b4b4: Tffa657StringTb4b4b4)

Tf0883e@Transaction
Tff7b72suspend Tff7b72fun Td2a8ffinstallConfigTb4b4b4(Te6edf3miTb4b4b4: Te6edf3MyNodeEntityTb4b4b4, Te6edf3nodesTb4b4b4: Te6edf3ListTff7b72<Te6edf3NodeEntityTff7b72>Tb4b4b4) Tb4b4b4{
Te6edf3clearMyNodeInfoTb4b4b4(Tb4b4b4)
Te6edf3setMyNodeInfoTb4b4b4(Te6edf3miTb4b4b4)
Te6edf3putAllTb4b4b4(Te6edf3nodesTb4b4b4.Te6edf3map Tb4b4b4{ Te6edf3getVerifiedNodeForUpsertTb4b4b4(Tffa657itTb4b4b4) Tb4b4b4}Tb4b4b4)
Tb4b4b4}

T8b949e/**
* Backfills longName and shortName columns from the user protobuf for nodes where these columns are NULL. This
* ensures search functionality works for all nodes. Skips placeholder/default users (hwModel == UNSET).
*/
Tf0883e@Transaction
Tff7b72suspend Tff7b72fun Td2a8ffbackfillDenormalizedNamesTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodes Tff7b72= Te6edf3getAllNodesSnapshotTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3nodesToUpdate Tff7b72=
Te6edf3nodes
Tb4b4b4.Te6edf3filter Tb4b4b4{ Te6edf3node Tff7b72-Tff7b72>
T8b949e// Only backfill if columns are NULL AND the user is not a placeholder (hwModel != UNSET)
Tb4b4b4(Te6edf3nodeTb4b4b4.Te6edf3longName Tff7b72=Tff7b72= Tff7b72null Tff7b72|Tff7b72| Te6edf3nodeTb4b4b4.Te6edf3shortName Tff7b72=Tff7b72= Tff7b72nullTb4b4b4) Tff7b72&Tff7b72& Te6edf3nodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3hw_model Tff7b72!Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3UNSET
Tb4b4b4}
Tb4b4b4.Te6edf3map Tb4b4b4{ Te6edf3node Tff7b72-Tff7b72> Te6edf3nodeTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3longName Tff7b72= Te6edf3nodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3long_nameTb4b4b4, Te6edf3shortName Tff7b72= Te6edf3nodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3short_nameTb4b4b4) Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3nodesToUpdateTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3putAllTb4b4b4(Te6edf3nodesToUpdateTb4b4b4)
Tb4b4b4}
Tb4b4b4}

Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffSELECT * FROM nodesTa5d6ff"Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffgetAllNodesSnapshotTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Te6edf3NodeEntityTff7b72>
Tb4b4b4}

Served by rngit 1.5.0 - Generated in 0.07s